PROFDINFO.COM

Votre enseignant d'informatique en ligne

Section 2

Formatage et filtrage

Retour à la page du cours

Cette section vous apprendra comment modifier la sortie de vos commandes afin qu'elle soit formatée exactement comme vous le désirez. Vous y verrez aussi comment filtrer les objets reçus par une commande afin de n'afficher que ceux qui correspondent à certaines spécifications précise.

Nous utiliserons en guise d'exemple les commandes reliées au contrôle des processus (programmes présentement en exécution sur l'ordinateur). Du coup, par effet de bord, vous apprendrez ces commandes et leurs possibilités en même temps. N'est-ce pas hyper-pédagogique?

Le formatage

Vous vous rappellerez sans doute que toute commande retourne des objets et non du texte. Ces objets sont ensuite convertis en texte pour être affichés à l'écran (mais pourraient être passés à une autre commande en tant qu'objet si nécessaire).

La majorité des commandes ont un formatage par défaut sous forme de tableau, avec certaines colonnes prédéfinies. Les colonnes correspondent à des attributs des objets retournés, mais ils ne sont généralement que la pointe de l'iceberg: tout un tas d'autres attributs potentiellement intéressants ne sont pas affichés par défaut.

On peut donc utiliser une des commandes de formatage pour modifier cet affichage selon nos besoins.

Les pipelines ("pipes")

Le pipeline permet de lier des commandes en envoyant la sortie d'une commande à l'entrée d'une autre. Comme toutes les commandes manipulent des objets et retournent des objets, le pipeline est beaucoup plus utile en PowerShell qui l'est dans tout autre langage de commandes qui manipule du texte.

Un pipeline est représenté par une barre verticale: |. En clavier Français (Canada), on l'obtient en faisant "majuscule" et la touche à gauche du 1 et au-dessus du "tab" (c'est la touche qui fait le backslash, elle peut très bien se trouver ailleurs sur vos claviers).

Selon votre système d'exploitation, il est possible que votre barre verticale apparaisse "trouée", comme si elle était composée de deux petites barres verticales l'une par-dessus l'autre. Ça ne changera rien à la fonctionnalité du pipeline (aucun objet ne s'échappera par le trou).

Le principe ici est qu'on va exécuter la commande qu'on veut, suivie d'un pipeline et d'une commande faite pour formater.

format-table: le format par défaut

Si vous exécutez la commande get-process et ensuite get-process | format-table, vous ne verrez aucune différence. Ceci nous confirme que format-table est appelée par défaut lorsqu'on ne spécifie aucun formatage.

On peut toutefois modifier le tableau obtenu en l'appelant explicitement, par exemple pour afficher certaines colonnes plutôt que d'autres.

Pour cela, c'est bien pratique de connaître les colonnes possibles. Rappelez-vous qu'une colonne correspond à un attribut de l'objet obtenu.

On peut donc faire get-process | get-member pour voir la description de l'objet retourné par get-process. On constate qu'il est de type System.Diagnostics.Process et qu'il contient tout un tas de membres. Parmi ces membres, ce qui nous intéressera sont les "Properties", donc les attributs. Les "ScriptProperties" et les "AliasProperties" peuvent être utilisées aussi. Les "Events" et les "Methods" ne peuvent pas être affichés.

Le paramètre -property de format-table permet d'énumérer une liste de propriétés à afficher, séparées par des virgules. Par exemple:

get-process | format-table -property name, id, TotalProcessorTime, PriorityClass, Responding

Il arrive parfois qu'on en demande trop et que l'affichage ne soit pas possible dans la largeur de la console. Par exemple:

get-process | format-table -property name, id, company, TotalProcessorTime, PriorityClass, Responding

À ce moment, certaines colonnes seront trop étroites et leur contenu sera tronqué (et se terminera par des "..."). On peut éviter ça en utilisant le paramètre -autosize de format-table. Celui-ci ajuste la largeur des colonnes en fonction des données qui s'y trouveront réellement (et non pas en fonction de données hypothétiques), afin que tout soit le plus étroit possible, mais sans tronquer quoi que ce soit:

get-process | format-table -property name, id, company, TotalProcessorTime, PriorityClass, Responding -autosize

Notez qu'il est possible dans ce cas que certaines colonnes ne soit pas affichées parce qu'il n'y a simplement pas assez d'espace pour tout. Dans ce cas, un avertissement nous sera donné avant l'affichage et les colonnes les plus à droite seront tronquées ou omises (format-table prend pour acquis que les colonnes de gauche sont les plus importantes, alors vous devriez faire de même!).

get-process | format-table -property name, id, company, TotalProcessorTime, PriorityClass, MachineName, MainWindowTitle, Responding -autosize

Dans ce cas, on peut utiliser le paramètre -wrap de format-table afin que le texte trop large pour une colonne soit réparti sur plusieurs lignes.

get-process | format-table -property name, id, company, TotalProcessorTime, PriorityClass, MachineName, MainWindowTitle, Responding -autosize -wrap

Notez que d'utiliser -wrap avec -autosize dans ce cas-ci n'est pas la meilleure solution puisqu'il ne "wrapera" que les colonnes trop étroites vers la droite et qu'il est tout de même possible que les dernières soient carrément omises (c'est le cas dans notre exemple). Il vaut mieux dans ce cas n'utiliser que -wrap (sans -autosize):

get-process | format-table -property name, id, company, TotalProcessorTime, PriorityClass, MachineName, MainWindowTitle, Responding -wrap

Sans -autosize, les colonnes sont toutes plus étroites par défaut (comme dans notre premier exemple), mais le -wrap fait en sorte qu'on ne perde pas d'information.

format-wide: moins détaillé, donc plus d'éléments dans l'écran

Le format large est produit le même effet que la switch -w de la commande dir: elle affiche beaucoup moins d'information pour chaque objet, ce qui permet d'en faire tenir beaucoup plus dans l'écran. Ça peut être utile lorsqu'on ne veut qu'une liste de noms, par exemple:

get-process | format-wide

Notez que par défaut, l'affichage est fait sur deux colonnes afin d'en faire tenir un maximum dans l'écran. On peut changer le nombre de colonnes avec le paramètre -column suivi d'un nombre. On peut également changer la (seule) propriété qui est affichée avec -property suivi du nom de cette propriété (mais bien peu de propriétés sont appropriées dans ce cas-ci).

format-list: plus détaillé, mais plus long

Le format liste se concentre sur le niveau de détail plutôt que sur l'ergonomie des informations. Le but ici est d'aller chercher tous les détails nécessaires, même si ça n'entrera pas dans un seul écran.

get-process | format-list

Pour chaque objet reçu, format-list affiche une liste d'attributs importants, un en dessous de l'autre, dans un format attribut : valeur. Les objets sont affichés les uns en dessous des autres, avec une ligne vide entre chacun.

Tout comme pour format-table, seulement les propriétés jugées les plus importantes sont affichées par défaut. On peut modifier ce comportement avec le paramètre -property suivi d'une liste de propriétés:

get-process | format-list -property name, id, company, TotalProcessorTime, PriorityClass, MachineName, MainWindowTitle, Responding

Ici, aucun besoin de -wrap ou de -autosize puisque tout entre toujours (la liste devient simplement très longue).

Notez que le paramètre -property accepte les caractères génériques et permet donc de faire:

get-process | format-list -property name, *memory*

Ou carrément, si vous êtes patients:

get-process | format-list -property *

Voilà un bon moment pour introduire la commande more (qui est en fait un alias (vers out-host -paging) créé pour que les habitués de DOS se sentent à l'aise):

get-process | format-list -property * | more

Mais ça demeure malgré tout plus utile de restreindre la liste des processus affichés par get-process:

get-process -name powershell | format-list -property *

Trier les résultats d'une commande

Fidèle à la mentalité PowerShell, la majorité des commandes n'offrent pas d'option de tri; il existe plutôt une commande qui sert à trier des objets quelconques et c'est sur elle qu'on se fie pour faire ça. La commande, c'est sort-object.

sort-object est très facile à utiliser: on lui passe n'importe quelle collection d'objets via le pipeline, on utilise le paramètre -property pour lui dire par quoi on veut trier, et elle fait son travail puis envoie le résultat sur le pipeline.

Par exemple:

get-process | sort-object -property id | format-table -property name, id, company, TotalProcessorTime, PriorityClass, MachineName, MainWindowTitle, Responding -wrap

Notez comment sort-object s'insère entre get-process et format-table sur le pipeline. get-process retoune l'ensemble des processus, sort-object les classe par PID, puis format-table les affiche dans le format voulu.

Sachez que sort-object a aussi deux autres paramètres utiles (et qui parlent d'eux-mêmes): -CaseSensitive et -descending.

Et apprenez aussi que format-table (voir plus haut) possède un paramètre -GroupBy qui permet de regrouper les entrées qui ont un point commun pour en faciliter la lecture. Dans ce cas, on dira à -GroupBy par quoi regrouper (et on s'assurera de ne pas mettre cette propriété dans les colonnes à afficher). Ça fonctionne beaucoup mieux si les objets sont tout d'abord triés en ordre de cette propriété.

Par exemple, tous les processus groupés par compagnie:

get-process | sort-object -property company | format-table -property name, id, Responding -GroupBy company

Filtrer les objets résultats avec where-object

Si les différentes commandes format peuvent modifier le format d'affichage et permettre de choisir quelles propriétés des objets seront affichés, la commande where-object permet de choisir quels objets seront conservés dans le résultat d'une commande. Ces objets conservés peuvent ensuite être affichés directement, envoyés à une commande format ou envoyés à toute autre commande, selon ce qu'on veut faire.

Évidemment, beaucoup de commandes ont des paramètres qui servent un peu à ça. Par exemple, on vient de voir que get-process a un paramètre -name qui permet d'afficher uniquement les processus qui portent un certain nom (notez que les caractères génériques peuvent aussi être utilisés pour ça!). Elle dispose aussi d'un paramètre -id qui permet de filtrer par le PID (process ID).

Mais qu'arrive-t-il si on veut filtrer par autre chose, comme par "responding" (pour ne voir que les processus qui ne répondent plus), par usage de la mémoire (pour ne voir que ceux qui en utilisent beaucoup) ou par n'importe quelle autre propriété?

C'est bien certain que chaque commande n'offrira pas des paramètres pour filtrer par chaque attribut possible. C'est là que where-object prend tout son sens.

where-object fonctionne avec le paramètre -FilterScript qui accepte un mini-script de filtration (en réalité, c'est tout simplement une condition, comme pour un if ou une boucle). Ce mini-script doit être placé {entre accolades}. where-object prend ensuite chacun des objets qu'elle reçoit en entrée (donc avec un pipeline), teste la condition puis retourne en sortie ceux pour qui cette condition est vraie. Cette sortie peut ensuite être envoyée n'importe où.

Il faut se rappeler d'une chose: les conditions qu'on utilisera seront toujours du type <attribut> <opérateur> <valeur>, puisqu'on comparera un attribut à une valeur, à l'aide d'un opérateur relationnel. L'attribut, par contre, est une partie de l'objet. On ne pourra pas faire simplement {responding -eq $false} puisque "responding" ne correspond à rien. Normalement, dans un script, on fera quelque chose comme:

foreach ($processus in get-process)
{
   if ($processus.responding -eq $false)
   {
      $processus | format-list *
   }
}

Ce script fait effectivement une filtration. Toutefois, ce qu'il fait avec les objets qui corresponde à son critère (ici, les processus qui ne répondent pas) est fixé d'avance: le script affiche l'objet en format liste, avec tous les attributs - il ne les retourne pas.

Ceci dit, remarquez la syntaxe de la condition: ($processus.responding -eq $false). Ici, la syntaxe a beaucoup plus de bon sens que dans mon exemple un peu raté ci-haut. En effet, responding est une propriété de $processus, variable contenant un objet de type Process. "responding" ne peut pas exister tout seul en soi, il faut le prendre dans une variable contenant un objet du bon type.

Comment faire alors avec where-object? On comprend que ceci n'est pas valable:

get-process | where-object -FilterScript {responding -eq $false}

"responding" doit suivre une variable. Dans notre script, c'était la variable $processus. Dans notre commande, il n'existe pas de variable. C'est pourquoi une pseudo-variable a été créée justement pour cet usage: $_. La variable $_ est utilisable dans le -FilterScript de where-object et représente l'objet en cours, l'objet qui est en ce moment analysé par where-object et qui provient de la commande précédente. On peut donc faire:

get-process | where-object -FilterScript {$_.responding -eq $false}

Évidemment, si vous n'avez aucun processus qui ne répond pas, la commande ne retournera rien...

Dans cet exemple, where-object reçoit une collection d'objets de get-process, les filtre et envoie ce qui passe le filtre à l'affichage par défaut. Mais on pourrait très bien les envoyer à une autre commande. Par exemple, essayez ceci:

get-process | where-object -FilterScript {$_.responding -eq $false} | format-list

On peut aussi les envoyer à la commande stop-process, qui arrête les processus!

get-process | where-object -FilterScript {$_.responding -eq $false} | stop-process

Vous voyez la beauté de PowerShell? En branchant ensemble plusieurs commandes, on peut accomplir exactement ce que l'on veut de façon intuitive.

Notez en passant que stop-process ne produit aucune sortie par défaut. Il ne fait qu'arrêter les processus qu'il reçoit en entrée (il existe d'autres façons de l'utiliser, faites un get-help stop-process pour plus de détails). Si on veut voir ce qu'il a arrêté au juste, on peut utiliser le paramètre -PassThru de stop-process:

get-process | where-object -FilterScript {$_.responding -eq $false} | stop-process -PassThru

Quelques exercices

(Voyez les réponses ici)

1- Indiquez la commande nécessaire pour afficher tous les processus qui utilisent plus de 10 megaoctets de mémoire (on parle ici du jeu de mémoire privée, c'est-à-dire l'espace mémoire assigné au processus qui ne peut pas être partagé - c'est généralement de ça qu'on parle quand on parle de la mémoire utilisée par un processus). Faites en sorte que votre commande indique le nom, le PID et la taille du jeu de mémoire privée, en format de tableau.

2- Faites la même chose qu'au numéro précédent, mais en triant les processus affichés en ordre décroissant de taille de mémoire.

3- Indiquez la commande nécessaire pour afficher tous les processus qui ont utilisé au moins 5 minutes de temps CPU. Affichez le résultat en format liste, en indiquant au minimum le nom du processus, son PID et son temps CPU.

4- Indiquez la commande nécessaire pour tuer d'un seul coup toutes les instances d'Internet Explorer qui roulent sur votre machine, tout en affichant la liste des processus ainsi tués.

5- Indiquez la commande nécessaire pour afficher tous les processus d'Internet Explorer qui roulent sur l'ordinateur d'un collègue dans la classe (il est possible que cette opération ne soit pas permise au P-113; indiquez tout de même quelle serait la commande).

6- Démarrez plusieurs instances de PowerShell. Indiquez la commande nécessaire pour tuer d'un seul coup toutes les instances de PowerShell sauf celle dans laquelle vous exécutez la commande, tout en affichant les processus tués. Vous aurez besoin de savoir qu'une variable prédéfinie permet d'identifier le processus du PowerShell courant - pour trouver laquelle, retournez voir la section sur les variables ici.

7- Indiquez la commande nécessaire pour afficher la liste de tous les services (attention, on ne parle plus des processus!) qui sont en ce moment en exécution sur votre machine (donc qui ne sont pas arrêtés). Utilisez le même affichage que celui par défaut, mais faites en sorte qu'on puisse bien lire le nom complet du service (DisplayName).

8- a) Indiquez la commande nécessaire pour voir la liste de toutes les propriétés possibles pour un service (sans rien afficher d'autre que des propriétés).

8- b) De quel type est un objet service?

9- Indiquez la commande nécessaire pour redémarrer tous les services qui peuvent être arrêtés (un service qui ne peut pas être arrêté ne peut donc pas être redémarré non plus) et qui roulent présentement. (Il est fort probable que vous ne pourrez pas le faire réellement au P-113, mais indiquez quand même la commande qui ferait la job.)

10- a) Indiquez la commande nécessaire pour tuer tous les processus dont le nom commence par s, mais en demandant une confirmation pour chacun d'eux (du coup, vous pourrez dire non à chacun!).

10-b) Quel paramètre auriez-vous pu utiliser pour simuler l'arrêt des processus en 10a) sans les tuer pour vrai?